/*
 * Copyright 2009 Max Kugland
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.splink.deepsplink.page {
	import org.splink.deepsplink.config.PageData;
	import org.splink.deepsplink.injector.IPageInjector;
	import org.splink.deepsplink.boot.IDeepsplinkFacade;
	
	import org.splink.library.tree.INode;
	import org.splink.library.tree.Tree;

	import flash.system.ApplicationDomain;

	/**
	 * <code>PageFactory</code> forges a <code>PageContainer</code> instance for each page clazz which is defined in 
	 * the configuration.  Each instance is also provided with a title, an <code>INavigationCommand</code> and the 
	 * public <code>Tree</code> datastructure. 
	 * If you need to inject more objects into a page you can add <code>IPageInjector</code> instances through the 
	 * <code>add</code> method. These injectors are provided with each freshly instantiated page. Make sure you add
	 * the injectors before the pages are instantiated, otherwise no injection can happen.
	 * 
	 * @author Max Kugland
	 */
	final public class PageFactory {
		private var _publicTree : Tree;
		private var _privateTree : Tree;
		private var _injectors : Array = new Array();
		private var _facade : IDeepsplinkFacade;

		/**
		 * @param publicTree the page tree data structure. Each <code>INode</code> contains a <code>PageData</code>
		 * instance in it's <code>data</code> property which corresponds to the node id. 		 * @param privateTree the page tree data structure. After the instantiation of the corresponding page each
		 * <code>INode</code> contains an <code>PageContainer</code> instance which corresponds to the node id. 
		 * @param facade the <code>IDeepsplinkFacade</code> from which an <code>INavigationCommand</code> is obtained
		 * which is injected into each <code>PageContainer</code> instance after instantiation
		 */
		public function PageFactory(publicTree : Tree, privateTree : Tree, facade : IDeepsplinkFacade) {
			_publicTree = publicTree;
			_privateTree = privateTree;
			_facade = facade;
			
			precheckPages();
		}
		
		/*
		 * Checks whether the configured pages are part of the current <code>ApplicationDomain</code>.
		 * The precheck makes sure that the most common error of not having any reference to a page inside
		 * the code pops up as early as possible. 
		 */
		private function precheckPages() : void {
			var ar : Array = _publicTree.toArray();
			for each (var node : INode in ar) {
				var pd : PageData = PageData(node.data);
				if(!ApplicationDomain.currentDomain.hasDefinition(pd.clazz)) {
					throw new Error("Class definition for class " + pd.id + " not found.");
				}
			}
		}
		
		/**
		 * In case of eager pages all configured pages are instantiated upfront. 
		 */
		public function preinstantiatePages() : void {
			var ar : Array = _privateTree.toArray();
			for each (var node : INode in ar) {
				createPage(node.id);
			}
		}

		/**
		 * Adds an <code>IPageInjector</code> which is useful if you need to inject extra objects into pages.
		 * 
		 * @param injector the injector
		 */
		public function addInjector(injector : IPageInjector) : void {
			_injectors.push(injector);
		}
		
		/**
		 * Instantiates the <code>PageContainer</code> which corresponds to the given <code>INode</code> and 
		 * stores it within the <code>_privateTree</code> data structure. Also checks whether the parent
		 * page instances are present and creates the missing ones.
		 * 
		 * @param node the <code>INode</code> for which to create the page
		 */
		public function createLazyPage(id : String) : void {
			var node : INode = Tree.getNodeById(id, _privateTree.root);
			
			if(!(node.data is IPageInternal)) {
				var parentChain : Array = Tree.getParentNodeChain(node);
				for each (var n : INode in parentChain) {
					if(!(n.data is IPageInternal)) {
						createPage(n.id);
					}
				}
				createPage(node.id);
			}
		}

		/**
		 * Creates the <code>Page</code>for the given id. Then injects <code>title</code>
		 * <code>navigationCommand</code> and <code>tree</code>. Finally returns the new
		 * <code>Page<code> instance. If you need to inject custom objects you 
		 * can add custom <code>IPageInjector</code> implementations via <code>addInjector</code>.
		 * 
		 * @param id the id for the <code>Page</code> to create
		 */
		public function createPage(id : String) : IPageInternal {
			var pageData : PageData = PageData(Tree.getNodeById(id, _publicTree.root).data);
			var node : INode = Tree.getNodeById(id, _privateTree.root);
		
			var container : IPageInternal = createPageConainer(pageData);
			var page : IPage = instantiatePage(id, container, pageData.clazz);
			container.setPage(page);
			runInjectors(page);
			node.data = container;
			return container;
		}

		private function runInjectors(page : IPage) : void {
			for each (var injector : IPageInjector in _injectors) {
				injector.injectPage(page);
			}
		}

		private function createPageConainer(pageData : PageData) : IPageInternal {
			var container : IPageInternal = new PageContainer(pageData.id);
			container.setTitle(pageData.title);
			container.setNavigationCommand(_facade.navigationCommand);
			container.setTree(_publicTree);
			return container;
		}
		
		private function instantiatePage(id : String, container : IPageInternal, clazz : String) : IPage {
			try {
				var p : Class = ApplicationDomain.currentDomain.getDefinition(clazz) as Class;
				return new p(container);
			} catch(e : Error) {
				throw new Error("Class definition not found for id " + id + " or error in constructor " + e.message);
			}
			return null;
		}
	}
}
